/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.lvyh.lightframe.core.provider.invoker;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.lvyh.lightframe.common.util.CommonUtils;
import com.lvyh.lightframe.core.cache.ServiceRegistryCache;
import com.lvyh.lightframe.core.invoke.request.RpcRequest;
import com.lvyh.lightframe.core.invoke.response.RpcResponse;
import com.lvyh.lightframe.common.util.ClassUtils;
import com.lvyh.lightframe.core.util.GenericObjectUtil;
import com.lvyh.lightframe.common.util.ThrowableUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cglib.reflect.FastClass;
import org.springframework.cglib.reflect.FastMethod;

import java.util.Map;

/**
 * @author lvyh 2021/05/23.
 */
public class MethodInvokeExecutor {
    private static final Logger logger = LoggerFactory.getLogger(MethodInvokeExecutor.class);

    public RpcResponse invoke(RpcRequest rpcRequest) {

        RpcResponse rpcResponse = new RpcResponse();
        rpcResponse.setRequestId(rpcRequest.getRequestId());

        String serviceUniqueCode = CommonUtils.buildServiceUniqueCode(rpcRequest.getClassName(), rpcRequest.getVersion());
        Object serviceBean = ServiceRegistryCache.get(serviceUniqueCode);

        if (serviceBean == null) {
            rpcResponse.setErrorMsg(String.format("can not find service bean by key: %s", serviceUniqueCode));
            return rpcResponse;
        }

        if (System.currentTimeMillis() - rpcRequest.getCreateMillisTime() > 1 * 60 * 1000) {
            rpcResponse.setErrorMsg("current request timeout.");
            return rpcResponse;
        }

        try {
            Class<?> serviceClass = serviceBean.getClass();
            String methodName = rpcRequest.getMethodName();
            Class<?>[] parameterTypes = null;
            String[] parameterTypesValue = null;
            Object[] parameters = null;
            boolean isGeneric = rpcRequest.isGeneric();
            if (isGeneric) {
                //generic invoke only support 1 input and output parameters
                parameterTypesValue = rpcRequest.getParameterTypesValue();
                if (parameterTypesValue != null && parameterTypesValue.length == 1) {
                    parameterTypes = new Class[parameterTypesValue.length];
                    for (int i = 0; i < parameterTypesValue.length; i++) {
                        parameterTypes[i] = ClassUtils.resolveClass(parameterTypesValue[i]);
                    }
                }
                parameters = rpcRequest.getParameters();
                if (parameters != null && parameters.length == 1) {
                    Object genericObject = parameters[0];
                    Map map = GenericObjectUtil.convertToObject(genericObject);
                    parameters[0] = JSONObject.parseObject(JSONObject.toJSONString(map), parameterTypes[0]);
                }

            } else {
                parameterTypes = rpcRequest.getParameterTypes();
                parameters = rpcRequest.getParameters();
            }

            FastClass serviceFastClass = FastClass.create(serviceClass);
            FastMethod serviceFastMethod = serviceFastClass.getMethod(methodName, parameterTypes);
            Object result = serviceFastMethod.invoke(serviceBean, parameters);
            logger.info("[MethodInvokeExecutor] provider complete service, request:{},result:{}", JSON.toJSONString(rpcRequest), JSON.toJSONString(result));

            rpcResponse.setResult(result);
        } catch (Throwable t) {
            logger.error("[MethodInvokeExecutor] provider invoke service error.", t);
            rpcResponse.setErrorMsg(ThrowableUtil.toString(t));
        }

        return rpcResponse;
    }
}
